<?php
/**
 * 执行SQL命令应用层类
 * 主要用于执行原生SQL语句，也属于数据库操作应用层。
 * 统一的查询接口及方法，不涉及SQL语句组装。
 *
 * @author Mo yi <root@imoi.cn>
 * @link   http://www.imoi.cn
 * @copyright Copyright &copy; 2010-2012 DuoLam Software LLC
 * @license http://www.imoi.cn/license/
 * @version $Id: dpDbCommand.php 2799 2012-04-22 14:41:54Z Mo.yi $
 * @package db
 * @since 1.0
 */

class dpDbCommand extends dpComponent
{

	/**
	 * 当前数据库连接句柄
	 */
	public    $current;

	/**
	 * SQL语句存储器
	 */
	private   $_sql = array();

	/**
	 * 操作的数据表，用于继承本类，单独操作一张表
	 */
	protected $tableName;

	/**
	 * 连接的数据库类型。设置从库，指定从库名字
	 * 用于继承本类简便操作！
	 */
	protected $link;

	/**
	 * 当前所使用的数据库系统信息
	 */
	protected $currentDriver;

	/**
	 * 配置信息
	 */
	private   $_dbInfo;

	/**
	 * SQL语句架构处理句柄
	 */
	private   $_schema;

	/**
	 * 是否预处理
	 */
	private   $_isPrepare = false;

	/**
	 * 预处理参数
	 */
	private   $_params = array();

	/**
	 * 全局缓存时间
	 */
	private   $_leftTime = -1;

	/**
	 * 构造函数
	 * @param 从库连接名
	 * @param 指定数据表
	 */
	public function __construct($link = null, $table = null)
	{
		empty($link)  or $this->link      = $link;
		empty($table) or $this->tableName = $table;
		$this->_dbInfo = $this->setConfig('component','db');
		$this->init();
	}

	/**
	 * 初始化
	 */
	public function init(){}

	/**
	 * 获取当前设置的从库名字
	 */
	public function getLink()
	{
		return $this->link
			   ? $this->link
			   : $this->setLink();
	}

	/**
	 * 重新设置从库
	 * @param string 从库名字
	 */
	public function resetLink($link)
	{
		$this->link = $link;
		return $this;
	}

	/**
	 * 设置连接从库，用于继承本类的模型重写该方法
	 * @return string  返回从库名字
	 */
	protected function setLink(){}

	/**
	 * 获取数据表名
	 */
	public function getTable()
	{
		return $this->tableName
			   ? $this->tableName
			   : $this->setTable();
	}

	/**
	 * 重新设置数据表名
	 */
	public function resetTable($tablename)
	{
		$this->tableName = $tablename;
	}

	/**
	 * 设置数据表名，用于继承本类，设置数据表
	 * @return string  返回数据表名
	 * 例：return {table}
	 * 表名前后带上大括号
	 */
	protected function setTable(){}

	/**
	 * 获取SQL语句处理句柄
	 */
	public function getSchema()
	{
		return $this->_schema;
	}

	/**
	 * 获取当前使用的数据库配置信息
	 */
	public function getCurrentDbInfo()
	{
		return $this->currentDriver;
	}

	/**
	 * 设置全局缓存过期时间
	 */
	public function setCache($time)
	{
		$this->_leftTime = (int)$time;
		return $this;
	}

	/**
	 * 获取设置的全局缓存过期时间
	 */
	public function getCache()
	{
		return $this->_leftTime;
	}

	/**
	 * 创建SQL接口
	 * @return object  类本身
	 */
	public function createSql($sql = null)
	{
		$this->link = empty($this->link) 
					? $this->getLink()
					: $this->link;
		$this->tableName = empty($this->tableName)
					? $this->getTable() 
					: $this->tableName;
		$this->connect();

		if (empty($sql))
			return $this;
		else if (!preg_match('/SELECT|SHOW/i', $sql))
			return $this->execute($sql);
		else
			return $this->finds($sql);
	}

	/**
	 * 连接数据库
	 */
	protected function connect()
	{
		$link = empty($this->link) ? 'master' : $this->link;

		if (isset($this->_dbInfo[$link]) and $link != 'slave')
			$this->currentDriver = $this->_dbInfo[$link];
		else if (isset($this->_dbInfo['slave'][$link]))
			$this->currentDriver = $this->_dbInfo['slave'][$link];

		$this->_dbInfo = null;
		$this->current = D::app()->getDb()->$link;

		if (is_object($this->current)) {
			$driver = $this->currentDriver['driver'];

			if ($driver == 'mysqli') $driver = 'mysql';

			if (stripos($driver,'pdo') !== false) {
				$param = explode(':',$driver);
				$d = &$param[1];
				if($d == 'sqlsrv' || $d == 'mssql' || $d == 'dblib') $d = 'mssql';
				$class = $param[1] . 'Schema';
				$this->_schema = D::app()->$class($this->current);
			} else {
				$class = $driver . 'Schema';
				$this->_schema = D::app()->$class($this->current);
			}
		}
	}

	/**
	 * 链式操作开始
	 */

	/**
	 * 链式操作：设置要查询的字段
	 * 如果参数为数组，key为字段名，value为字段别名
	 * @param string or array $columns 字段列表，默认取出所有
	 * @return object 类本身
	 */
	public function select($columns = '*')
	{
		if (!empty($this->_sql)) {
			$this->clearSql();
		}
		$this->_sql['select'] = $this->_schema->getSelectString($columns);
		return $this;
	}

	/**
	 * 链式操作：设置要查询的字段（去除重复结果版）
	 * 如果参数为数组，key为字段名，value为字段别名
	 * @param string or array $columns 字段列表，默认取出所有
	 * @return object 类本身
	 */
	public function selectDistinct($columns = '*')
	{
		$this->_sql['select'] = $this->_schema->getSelectString($columns,true);
		return $this;
	}

	/**
	 * 链式操作：设置要查询的表
	 * 不填写，使用设置的统一表名
	 * 如果参数为数组，key为表名，value为表别名
	 * @param string or array $table 表列表
	 * @return object 类本身
	 */
	public function from($table = null)
	{
		$table = empty($table) ? $this->getTable() : $table;
		$this->_sql['from'] = $this->_schema->getFromString($table);
		return $this;
	}

	/**
	 * 链式操作：设置要inner连接查询的表
	 * @param string or array $table      表名称
	 * @param string or array $conditions 条件
	 * @param array           $params     预处理绑定的参数
	 * @return object 类本身
	 */
	public function join($table, $conditions, $params = array())
	{
		if (empty($this->_sql['join']))
			$this->_sql['join'] = null;

		if (!empty($params))
			$this->setPrepareParams($params);

		$this->_sql['join'] .= $this->_schema->getJoinString($table,'inner',$conditions);
		return $this;
	}

	/**
	 * 链式操作：设置要full连接查询的表
	 * @param string or array $table      表名称
	 * @param string or array $conditions 条件
	 * @param array           $params     预处理绑定的参数
	 * @return object 类本身
	 */
	public function fullJoin($table, $conditions, $params = array())
	{
		if (empty($this->_sql['join']))
			$this->_sql['join'] = null;

		if (!empty($params))
			$this->setPrepareParams($params);

		$this->_sql['join'] .= $this->_schema->getJoinString($table,'full',$conditions);
		return $this;
	}

	/**
	 * 链式操作：设置要left连接查询的表
	 * @param string or array $table      表名称
	 * @param string or array $conditions 条件
	 * @param array           $params     预处理绑定的参数
	 * @return object 类本身
	 */
	public function leftJoin($table, $conditions, $params = array())
	{
		if (empty($this->_sql['join']))
			$this->_sql['join'] = null;

		if (!empty($params))
			$this->setPrepareParams($params);

		$this->_sql['join'] .= $this->_schema->getJoinString($table,'left',$conditions);
		return $this;
	}

	/**
	 * 链式操作：设置要right连接查询的表
	 * @param string or array $table      表名称
	 * @param string or array $conditions 条件
	 * @param array           $params     预处理绑定的参数
	 * @return object 类本身
	 */
	public function rightJoin($table, $conditions, $params = array())
	{
		if (empty($this->_sql['join']))
			$this->_sql['join'] = null;
		if (!empty($params))
			$this->setPrepareParams($params);

		$this->_sql['join'] .= $this->_schema->getJoinString($table,'right',$conditions);
		return $this;
	}

	/**
	 * 链式操作：设置要查询条件
	 * @param string or array $conditions 条件
	 * @param array           $params     预处理绑定的参数
	 * @return object 类本身
	 */
	public function where($conditions, $params = array())
	{
		if (!empty($params)) $this->setPrepareParams($params);

		$this->_sql['where'] = $this->_schema->getWhereString($conditions);
		return $this;
	}

	/**
	 * 链式操作：设置要分组的字段
	 * @param string $columns    字段名
	 * @return object 类本身
	 */
	public function group($columns)
	{
		$this->_sql['group'] = $this->_schema->getGroupString($columns);
		return $this;
	}

	/**
	 * 链式操作：设置要having的条件
	 * @param string or array $conditions 条件
	 * @param array           $params     预处理绑定的参数
	 * @return object 类本身
	 */
	public function having($conditions, $params = array())
	{
		if (!empty($params)) $this->setPrepareParams($params);

		$this->_sql['having'] = $this->_schema->getHavingString($conditions);
		return $this;
	}

	/**
	 * 链式操作：设置要排序的字段
	 * @param string $columns 字段名
	 * @return object 类本身
	 */
	public function order($columns)
	{
		$this->_sql['order'] = $this->_schema->getOrderString($columns);
		return $this;
	}

	/**
	 * 链式操作：设置限制查询行数
	 * @param int $limit  起始行数
	 * @param int $offset 行数限制
	 * @return object 类本身
	 */
	public function limit($limit, $offset=null)
	{
		$this->_sql['limit'] = $this->_schema->getLimitString($limit, $offset, $this->_sql);
		return $this;
	}

	/*
	 * 以下是执行SQL方法
	 */

	/**
	 * 链式操作：取出一行数据，也可单独使用
	 * @param string $sql
	 * @param array  $params 预处理绑定的参数
	 * @return array 结果集
	 */
	public function find($sql = null, $params = array())
	{
		if ($sql === null) {
			if (empty($this->_params))
				return $this->current->find($this->_parseSql(), $this->getCache());
			else
				return $this->_prepare($this->_parseSql(), $this->_params, true);
		} else {
			if ($params === array())
				return $this->current->find($sql, $this->getCache());
			else
				return $this->_prepare($sql, $params, true);
		}
	}

	/**
	 * 链式操作：取出多行数据，也可单独使用
	 * @param string $sql
	 * @param array  $params 预处理绑定的参数
	 * @return array 结果集
	 */
	public function finds($sql = null, $params = array())
	{
		if ($sql === null) {
			if (empty($this->_params))
				return $this->current->finds($this->_parseSql(), $this->getCache());
			else
				return $this->_prepare($this->_parseSql(), $params, true, true);
		} else {
			if ($params === array())
				return $this->current->finds($sql, $this->getCache());
			else
				return $this->_prepare($sql, $params, true, true);
		}
	}

	/**
	 * 链式操作：统计取出的数据行数，也可单独使用
	 * @param string $sql
	 * @return int 统计的行数
	 */
	public function count($sql = null)
	{
		if ($sql === null)
			return $this->current->count();
		else
			return $this->current->count($sql);
	}

	/**
	 * 执行非查询的SQL语句
	 * @param string $sql
	 * @param array  $params 预处理绑定的参数
	 * @return int 最后insert的ID 或者 上次操作影响的行数
	 */
	public function execute($sql, $params = array())
	{
		if ($params === array())
			return $this->current->execute($sql);
		else
			return $this->_prepare($sql, $params);
	}

	/**
	 * 执行insert操作
	 * @param string $table   表名称
	 * @param array  $columns key为字段名，value为值
	 * @param array  $params  预处理绑定的参数
	 * @return array 返回insert ID
	 */
	public function insert($table = null, $columns, $params = array())
	{
		$table = empty($table) ? $this->tableName : $table;
		$sql = $this->_schema->getInsertString($table, $columns);
		return $this->execute($sql, $params);
	}

	/**
	 * 执行update操作
	 * @param string $table   表名称
	 * @param array  $columns key为字段名，value为值
	 * @param string or array $conditions 更新记录条件
	 * @param array  $params  预处理绑定的参数
	 * @return array 返回影响的行数
	 */
	public function update($table = null, $columns, $conditions, $params = array())
	{
		$table = empty($table) ? $this->tableName : $table;
		$sql = $this->_schema->getUpdateString($table, $columns, $conditions);
		return $this->execute($sql, $params);
	}

	/**
	 * 执行delete操作
	 * @param string $table   表名称
	 * @param string or array $conditions 更新记录条件
	 * @param array  $params  预处理绑定的参数
	 * @return array 返回影响的行数
	 */
	public function delete($table = null, $conditions, $params = array())
	{
		$table = empty($table) ? $this->tableName : $table;
		$sql = $this->_schema->getDelteString($table, $conditions);
		return $this->execute($sql, $params);
	}

	/*
	 * 链式操作结束
	 */


	/*
	 * 架构操作方法
	 */

	/**
	 * 获取表中字段信息
	 * @param string $table  表名称
	 * @return array 表结构
	 */
	public function getColumn($table)
	{
		return $this->_schema->getColumn($table);
	}

	/**
	 * 创建数据表
	 * @param string $name    表名称
	 * @param array  $columns 字段列表
	 * @param string $options 表扩展设置，比如表的引擎类型，设置字段的索引
	 * @return boolean 成功返回true
	 */
	public function createTable($name, $columns, $options = null)
	{
		$this->execute($this->_schema->getCreateTableString($name, $columns, $options));
		return true;
	}

	/**
	 * 删除数据表
	 * @param string $name  表名称
	 * @return boolean 成功返回true
	 */
	public function dropTable($name)
	{
		$this->execute($this->_schema->getDropTableString($name));
		return true;
	}

	/**
	 * 修改表名称
	 * @param string $name    表名称
	 * @param string $newName 表的新名称
	 * @return boolean 成功返回true
	 */
	public function renameTable($name, $newName)
	{
		$this->execute($this->_schema->getRenameTableString($name, $newName));
		return true;
	}

	/**
	 * 清空数据表
	 * @param string $table  表名称
	 * @param string $pk     该表的主键
	 * @return boolean 成功返回true
	 */
	public function truncateTable($table)
	{
		$sql = $this->_schema->getTruncateTable($table);

		if ($sql !== true) $this->execute($sql);

		return true;
	}


	/**
	 * 添加字段
	 * @param string $table  表名称
	 * @param string $name 	 字段名
	 * @param string $type   字段数据类型
	 * @return boolean 成功返回true
	 */
	public function addColumn($table, $name, $type)
	{
		$this->execute($this->_schema->getAddColumnString($table, $name, $type));
		return true;
	}

	/**
	 * 删除字段
	 * @param string $table  表名称
	 * @param string $name 	 字段名
	 * @return boolean 成功返回true
	 */
	public function dropColumn($table, $name)
	{
		$this->execute($this->_schema->getDropColumnString($table, $name));
		return true;
	}

	/**
	 * 重命名字段
	 * @param string $table  表名称
	 * @param string $name 	 字段名
	 * @param string $type   新字段名
	 * @return boolean 成功返回true
	 */
	public function renameColumn($table, $name, $newName)
	{
		$this->execute($this->_schema->getRenameColumnString($table, $name, $newName));
		return true;
	}

	/**
	 * 修改字段数据类型
	 * @param string $table  表名称
	 * @param string $name 	 字段名
	 * @param string $type   字段新的数据类型
	 * @return boolean 成功返回true
	 */
	public function alterColumn($table, $name, $type)
	{
		$this->execute($this->_schema->getAlterColumnString($table, $name, $type));
		return true;
	}

	/*
	 * 其他特殊方法
	 */

	/**
	 * 获取执行的SQL语句
	 * @return string 返回最后一次执行的SQL语句 
	 */
	public function getSql()
	{
		$key = array
		(
			'select'=>'select',
			'from'=>'from',
			'join'=>'join',
			'where'=>'where',
			'group'=>'group',
			'having'=>'having',
			'order'=>'order',
			'limit'=>'limit'
		);
		$sql = null;

		foreach ($this->_sql as $k => $v) {
			$sql .= isset($this->_sql[$key[$k]])
				 ? $this->_sql[$key[$k]] 
				 : '';
		}

		return $this->current->getDealTablePrefix($sql);
	}

	/**
	 * 清理执行的sql语句
	 */
	public function clearSql()
	{
		$this->_sql = array();
		return $this;
	}

	/**
	 * 解析SQL语句
	 * @return string SQL语句
	 */
	private function _parseSql()
	{
		$sql = null;
		$sql .= $this->_getCheckVar('select');
		$sql .= $this->_getCheckVar('from');
		$sql .= $this->_getCheckVar('join');
		$sql .= $this->_getCheckVar('where');
		$sql .= $this->_getCheckVar('group');
		$sql .= $this->_getCheckVar('having');
		$sql .= $this->_getCheckVar('order');
		$sql .= $this->_getCheckVar('limit');
		return $sql;
	}

	/**
	 * 预处理SQL语句处理
	 * 
	 * @param string $sql SQL语句
	 * @param array  $params 绑定的参数
	 * @param boolean $isReturn 是否返回结果集
	 * @param boolean $isAll    是否多行数据
	 * @return int or array
	 */
	private function _prepare($sql, $params, $isReturn = false , $isAll = false)
	{
		$driver = explode(':',$this->currentDriver['driver']);

		if ($driver[0] == 'pdo')
			$this->current->prepare($sql)->bindValue($params);
		else
			$this->current->prepare($sql)->bindParam($params);

		if (!$isReturn)
			return $this->current->stmtQuery();
		else
			return $isAll ? $this->current->stmtFinds($this->getCache()) : $this->current->stmtFind($this->getCache());
	}

	/**
	 * 处理SQL语句
	 */
	private function _getCheckVar($var)
	{
		return isset($this->_sql[$var]) ? "\n" . $this->_sql[$var] : null;
	}

	public function __get($name)
	{
		isset($this->$name) or
		dpException::error('调用的属性不存在，'.$name.'，dpDbCommand');
	}

	public function __call($name,$param)
	{
		method_exists($this, $name) or 
		dpException::error('调用的方法不存在，'.$name.'()，dpDbCommand');
	}

	/**
	 * 设置预处理参数
	 */
	public function setPrepareParams($params)
	{
		foreach ($params as $k =>$v) $this->_params[$k] = $v;
	}

	/**
	 * 获取预处理参数
	 */
	public function getPrePareParams()
	{
		return $this->_params;
	}

	/**
	 * 获取所查询的SQL语句
	 */
	public function getQuerySql()
	{
		return $this->current->getQuerySql();
	}

	/**
	 * 获取页面查询次数
	 */
	public function getQueryNum()
	{
		return $this->current->getQueryNum();
	}

}

?>